package minijava.Frame.Mips;
import java.util.HashMap;
import java.util.List;
import java.util.LinkedList;
import java.util.Iterator;
import java.util.ListIterator;
//import java.util.Arrays;
import minijava.Frame.*;
//import minijava.visitor.SymbolTable;
import minijava.Temp.*;


/*
 * Algumas informacoes encontradas para auxilio na construcao do frame MIPS
 * Fonte: http://www.cesarkallas.net/arquivos/faculdade/arquitetura-computadores/teoria/cap3hp.pdf
 * 32 Registradores de 32 bits($0..$31)
 * Convencao para registradores MIPS -
 *   $zero - $0 - Constante 0
 *   $v0 - $2 - Resultado e avaliacoes de expressoes - Nao preservado
 *   $v1 - $3 - Resultado e avaliacoes de expressoes - Nao preservado
 *   $a0 - $4 - Argumento - Preservado
 *   $a1 - $5 - Argumento - Preservado
 *   $a2 - $6 - Argumento - Preservado
 *   $a3 - $7 - Argumento - Preservado
 *   $t0 - $8 - Temporario - Nao Preservado
 *   $t1 - $9 - Temporario - Nao Preservado
 *   $t2 - $10 - Temporario - Nao Preservado
 *   $t3 - $11 - Temporario - Nao Preservado
 *   $t4 - $12 - Temporario - Nao Preservado
 *   $t5 - $13 - Temporario - Nao Preservado
 *   $t6 - $14 - Temporario - Nao Preservado
 *   $t7 - $15 - Temporario - Nao Preservado
 *   $s0 - $16 - Salvos - Preservado
 *   $s1 - $17 - Salvos - Preservado
 *   $s2 - $18 - Salvos - Preservado
 *   $s3 - $19 - Salvos - Preservado
 *   $s4 - $20 - Salvos - Preservado
 *   $s5 - $21 - Salvos - Preservado
 *   $s6 - $22 - Salvos - Preservado
 *   $s7 - $23 - Salvos - Preservado
 *   $t8 - $24 - Temporario - Nao Preservado
 *   $t9 - $25 - Temporario - Nao Preservado
 *   $gp - $28 - Ponteiro Global - Preservado
 *   $sp - $29 - Stack Pointer - Preservado
 *   $fp - $30 - Frame Pointer - Preservado
 *   $ra - $31 - Endereco de retorno - Preservado
 */


public class MipsFrame extends Frame {

    // Registradores descritos acima
    static final Temp ZERO = new Temp(); 
    static final Temp AT = new Temp();
    static final Temp V0 = new Temp();
    static final Temp V1 = new Temp();
    static final Temp A0 = new Temp();
    static final Temp A1 = new Temp();
    static final Temp A2 = new Temp();
    static final Temp A3 = new Temp();
    static final Temp T0 = new Temp();
    static final Temp T1 = new Temp();
    static final Temp T2 = new Temp();
    static final Temp T3 = new Temp();
    static final Temp T4 = new Temp();
    static final Temp T5 = new Temp();
    static final Temp T6 = new Temp();
    static final Temp T7 = new Temp();
    static final Temp S0 = new Temp();
    static final Temp S1 = new Temp();
    static final Temp S2 = new Temp();
    static final Temp S3 = new Temp();
    static final Temp S4 = new Temp();
    static final Temp S5 = new Temp();
    static final Temp S6 = new Temp();
    static final Temp S7 = new Temp();
    static final Temp T8 = new Temp();
    static final Temp T9 = new Temp();
    static final Temp K0 = new Temp();
    static final Temp K1 = new Temp();
    static final Temp GP = new Temp();
    static final Temp SP = new Temp();
    static final Temp S8 = new Temp();
    static final Temp RA = new Temp();

    private static final Temp[]
	specialRegs = { ZERO, AT, K0, K1, GP, SP }, //reservados para propositos especiais
	argRegs	= { A0, A1, A2, A3 }, //argumentos
	calleeSaves = { RA, S0, S1, S2, S3, S4, S5, S6, S7, S8 }, //salvos pelo chamado
	callerSaves = { T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, V0, V1 }; //salvos pelo chamador

    private static final HashMap<Temp,String> tempMap = new HashMap<Temp,String>(32);
    static {
        tempMap.put(ZERO, "$0");
	tempMap.put(AT,   "$at");
	tempMap.put(V0,   "$v0");
	tempMap.put(V1,   "$v1");
	tempMap.put(A0,   "$a0");
	tempMap.put(A1,   "$a1");
	tempMap.put(A2,   "$a2");
	tempMap.put(A3,   "$a3");
	tempMap.put(T0,   "$t0");
	tempMap.put(T1,   "$t1");
	tempMap.put(T2,   "$t2");
	tempMap.put(T3,   "$t3");
	tempMap.put(T4,   "$t4");
	tempMap.put(T5,   "$t5");
	tempMap.put(T6,   "$t6");
	tempMap.put(T7,   "$t7");
	tempMap.put(S0,   "$s0");
	tempMap.put(S1,   "$s1");
	tempMap.put(S2,   "$s2");
	tempMap.put(S3,   "$s3");
	tempMap.put(S4,   "$s4");
	tempMap.put(S5,   "$s5");
	tempMap.put(S6,   "$s6");
	tempMap.put(S7,   "$s7");
	tempMap.put(T8,   "$t8");
	tempMap.put(T9,   "$t9");
	tempMap.put(K0,   "$k0");
	tempMap.put(K1,   "$k1");
	tempMap.put(GP,   "$gp");
	tempMap.put(SP,   "$sp");
	tempMap.put(S8,   "$fp");
	tempMap.put(RA,   "$ra");
    }

    private static Temp[] registers = {};
    {
	LinkedList<Temp> l = new LinkedList<Temp>();
	addAll(l, callerSaves);
	addAll(l, calleeSaves);
	addAll(l, argRegs);
	addAll(l, specialRegs);
	registers = l.toArray(registers);
    }


    // Registradores vivos no retorno
    private static Temp[] returnSink = {};
    {
	LinkedList<Temp> l = new LinkedList<Temp>();
	l.add(V0);
	addAll(l, specialRegs);
	addAll(l, calleeSaves);
	returnSink = l.toArray(returnSink);
    }

    // Registradores salvos pelo chamador
    static Temp[] calldefs = {};
    {
	LinkedList<Temp> l = new LinkedList<Temp>();
	l.add(RA);
	addAll(l, argRegs);
	addAll(l, callerSaves);
	calldefs = l.toArray(calldefs);
    }

    private static final int wordSize = 4;
    private int offset = 0;
    static final Temp FP = new Temp(); // virtual frame pointer
    private static final Label badPtr = new Label("BADPTR");
    private static final Label badSub = new Label("BADSUB");
    int maxArgOffset = 0;
    private static boolean spilling = false ;
    // set spilling to true when the spill method is implemented
    
    @Override
    public Temp FP() { return FP; }

    @Override
    public Temp RV() { return V0; }

    @Override
    public Temp R0() { return ZERO; }

    @Override
    public int wordSize() { return wordSize; }

    @Override
    public Temp[] registers() { return registers; }

    public Label badPtr() { return badPtr; }
    public Label badSub() { return badSub; }
    public String tempMap(Temp temp) { return tempMap.get(temp); }
    private static HashMap<String,Integer> functions = new HashMap<String,Integer>();
    private static HashMap<String,Label> labels = new HashMap<String,Label>();

    /* Construtor vazio */
    public MipsFrame() {}

    /*
     * Criação de um novo frame
     *     Label (nome) do novo frame, e a lista de formais
     */
    @Override
    public Frame newFrame(Label name, List<Boolean> formals) {
        if (this.name != null) name = new Label(this.name + "." + name);
        return new MipsFrame(name, formals);
    }

    /*
     * Eh necessario verificar se ja existe outra funcao
     * no frame com o mesmo nome. Caso exista, temos que
     * modificar o nome de acordo com a ideia dada pelo livro
     */
    private MipsFrame(Label n, List<Boolean> f) {
	Integer count = functions.get(n.toString());
	if (count == null) {
	    count = new Integer(0);
	    name = n;
	} else {
	    count = new Integer(count.intValue() + 1);
	    name = new Label(n.toString() + "." + count);
	}
	functions.put(n.toString(),count);
        actuals = new LinkedList<Access>();
	formals = new LinkedList<Access>();
	//int offset = 0;
        Iterator<Boolean> escapes = f.iterator();
	formals.add(allocLocal(escapes.next().booleanValue()));
	actuals.add(new InReg(V0));
	for (int i = 0; i < argRegs.length; ++i) {
	    if (!escapes.hasNext())
		break;
	    offset += wordSize;
	    actuals.add(new InReg(argRegs[i]));
	    if (escapes.next().booleanValue())
		formals.add(new InFrame(offset));
	    else
		formals.add(new InReg(new Temp()));
	}
	while (escapes.hasNext()) {
	    offset += wordSize;
            Access actual = new InFrame(offset);
	    actuals.add(actual);
	    if (escapes.next().booleanValue())
		formals.add(actual);
	    else
		formals.add(new InReg(new Temp()));
	}
    }

    /*
     * Dependendo da variavel escape, a alocacao deve ser no frame ou no registro
     * Caso a variavel escape, entao sera no frame, sera criado um objeto InFrame
     * com o argumento do offset atual (funciona como se fosse o local de memoria),
     * Caso nao escape, entao é criado um InReg com o registrador onde a variavel
     * sera guardada
     */
    public Access allocLocal(boolean escape) {
	if (escape) {
	    Access result = new InFrame(offset);
	    offset -= wordSize;
	    return result;
	} else
	    return new InReg(new Temp());
    }
   
    @Override
    public minijava.ArvoreIntermediaria.Expr externalCall(String s, List<minijava.ArvoreIntermediaria.Expr> args) {
	String func = s.intern();
	Label l = labels.get(func);
	if (l == null) {
	    l = new Label("_" + func);
	    labels.put(func, l);
	}

        args.add(0, new minijava.ArvoreIntermediaria.CONST(0));
        
        //Construindo um ExpList para criacao da Expr Call
        minijava.ArvoreIntermediaria.ExpList expl = null;
        for(int i=0; i < args.size()-1; i++)
        {
            expl = new minijava.ArvoreIntermediaria.ExpList(args.get(0),expl);
        }
	return new minijava.ArvoreIntermediaria.CALL(new minijava.ArvoreIntermediaria.NAME(l),expl);
    }

    
        private static <R> void addAll(java.util.Collection<R> c, R[] a) {
	for (int i = 0; i < a.length; i++)
	    c.add(a[i]);
    }


    private static minijava.ArvoreIntermediaria.Stm SEQ(minijava.ArvoreIntermediaria.Stm left, minijava.ArvoreIntermediaria.Stm right) {
	if (left == null)
	    return right;
	if (right == null)
	    return left;
	return new minijava.ArvoreIntermediaria.SEQ(left, right);
    }
    private static minijava.ArvoreIntermediaria.MOVE MOVE(minijava.ArvoreIntermediaria.Expr d, minijava.ArvoreIntermediaria.Expr s) {
	return new minijava.ArvoreIntermediaria.MOVE(d, s);
    }
    private static minijava.ArvoreIntermediaria.TEMP TEMP(Temp t) {
	return new minijava.ArvoreIntermediaria.TEMP(t);
    }

    private void assignFormals(Iterator<Access> formals, Iterator<Access> actuals, List<minijava.ArvoreIntermediaria.Stm> body)
    {
	if (!formals.hasNext() || !actuals.hasNext())
	    return;
	Access formal = formals.next();
	Access actual = actuals.next();
	assignFormals(formals, actuals, body);
	body.add(0, MOVE(formal.exp(TEMP(FP)), actual.exp(TEMP(FP))));
    }

    private void assignCallees(int i, List<minijava.ArvoreIntermediaria.Stm> body)
    {
	if (i >= calleeSaves.length)
	    return;
	Access a = allocLocal(!spilling);
	assignCallees(i+1, body);
	body.add(0, MOVE(a.exp(TEMP(FP)), TEMP(calleeSaves[i])));
	body.add(MOVE(TEMP(calleeSaves[i]), a.exp(TEMP(FP))));
    }

    private List<Access> actuals;
    public void procEntryExit1(List<minijava.ArvoreIntermediaria.Stm> body) {
	assignFormals(formals.iterator(), actuals.iterator(), body);
	assignCallees(0, body);
    }

    /*
    */
    
    //Mini Java Library will be appended to end of
    //program
    public String programTail(){

	return
	    "         .text            \n" +
	    "         .globl _halloc   \n" +
	    "_halloc:                  \n" +
	    "         li $v0, 9        \n" +
	    "         syscall          \n" +
	    "         j $ra            \n" +
	    "                          \n" +
	    "         .text            \n" +
	    "         .globl _printint \n" +
	    "_printint:                \n" +
	    "         li $v0, 1        \n" +
	    "         syscall          \n" +
	    "         la $a0, newl     \n" +
	    "         li $v0, 4        \n" +
	    "         syscall          \n" +
	    "         j $ra            \n" +
	    "                          \n" +
	    "         .data            \n" +
	    "         .align   0       \n" +
	    "newl:    .asciiz \"\\n\"  \n" +
	    "         .data            \n" +
	    "         .align   0       \n" +
	    "str_er:  .asciiz \" ERROR: abnormal termination\\n\" "+
	    "                          \n" +
	    "         .text            \n" +
	    "         .globl _error    \n" +
	    "_error:                   \n" +
	    "         li $v0, 4        \n" +
	    "         la $a0, str_er   \n" +
	    "         syscall          \n" +
	    "         li $v0, 10       \n" +
	    "         syscall          \n" ;
    }

    /* Nao implementado
     *
            private static Assem.Instr OPER(String a, Temp[] d, Temp[] s) {
                return new Assem.OPER(a, d, s, null);
            }

            public void procEntryExit2(List<Assem.Instr> body) {
                body.add(OPER("#\treturn", null, returnSink));
            }

            public void procEntryExit3(List<Assem.Instr> body) {
                int frameSize = maxArgOffset - offset;
                ListIterator<Assem.Instr> cursor = body.listIterator();
                cursor.add(OPER("\t.text", null, null));
                cursor.add(OPER(name + ":", null, null));
                cursor.add(OPER(name + "_framesize=" + frameSize, null, null));
                if (frameSize != 0) {
                    cursor.add(OPER("\tsubu $sp " + name + "_framesize",
                                    new Temp[]{SP}, new Temp[]{SP}));
                    body.add(OPER("\taddu $sp " + name + "_framesize",
                                  new Temp[]{SP}, new Temp[]{SP}));
                }
                body.add(OPER("\tj $ra", null, new Temp[]{RA}));
            }
     *
            public List<Assem.Instr> codegen(List<Tree.Stm> stms) {
                List<Assem.Instr> insns = new java.util.LinkedList<Assem.Instr>();
                Codegen cg = new Codegen(this, insns.listIterator());
                for (java.util.Iterator<Tree.Stm> s = stms.iterator(); s.hasNext(); )
                    s.next().accept(cg);
                return insns;
            }
     *
     *


             public void spill(List<Assem.Instr> insns, Temp[] spills) {
                if (spills != null) {
                    if (!spilling) {
                        for (int s = 0; s < spills.length; s++)
                            System.err.println("Need to spill " + spills[s]);
                        throw new Error("Spilling unimplemented");
                    }
                    else for (int s = 0; s < spills.length; s++) {
                        Tree.Exp exp = allocLocal(true).exp(TEMP(FP));
                        for (ListIterator<Assem.Instr> i = insns.listIterator();
                             i.hasNext(); ) {
                            Assem.Instr insn = i.next();
                            Temp[] use = insn.use;
                            if (use != null)
                                for (int u = 0; u < use.length; u++) {
                                    if (use[u] == spills[s]) {
                                        Temp t = new Temp();
                                        t.spillTemp = true;
                                        Tree.Stm stm = MOVE(TEMP(t), exp);
                                        i.previous();
                                        stm.accept(new Codegen(this, i));
                                        if (insn != i.next())
                                            throw new Error();
                                        insn.replaceUse(spills[s], t);
                                        break;
                                    }
                                }
                            Temp[] def = insn.def;
                            if (def != null)
                                for (int d = 0; d < def.length; d++) {
                                    if (def[d] == spills[s]) {
                                        Temp t = new Temp();
                                        t.spillTemp = true;
                                        insn.replaceDef(spills[s], t);
                                        Tree.Stm stm = MOVE(exp, TEMP(t));
                                        stm.accept(new Codegen(this, i));
                                        break;
                                    }
                                }
                        }
                    }
                }
            }
     */


}







